using System;
using Microsoft.Xna.Framework.Input;
using Ruminate.DataStructures;

namespace Ruminate.GUI.Framework {

    /// <summary>
    /// Handles/triggers all of the mouse or keyboard events. State management occurs 
    /// asynchronously while triggers are handled synchronously.
    /// </summary>
    public class InputManager {

        /// <summary>
        /// When ever these Elements are not not null they are in the state
        /// specified by their names. Used to trigger events and by the element
        /// class to see what state the element is in. 
        /// </summary>
        internal Element HoverElement { get; private set; }
        internal Element PressedElement { get; private set; }
        internal Element FocusedElement { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        private readonly TreeNode<Element> _rootNode;

        /// <summary>
        /// Ties into the events in InputSystem. Top events are asynchronous and 
        /// used for state management. The rest of them are synchronous and used to
        /// trigger user specified event handlers.
        /// </summary>
        internal InputManager(TreeNode<Element> rootNode) {

            /* ## Events to Manage Statement ## */

            InputSystem.MouseMove += delegate(Object o, MouseEventArgs e) {
                if (HoverElement != null) {
                    var lastHover = HoverElement;
                    FindHover(_rootNode);

                    if (lastHover != HoverElement) {
                        lastHover.ExitHover(e);
                        HoverElement.EnterHover(e);
                    }
                } else {
                    FindHover(_rootNode);
                    if (HoverElement != null) {
                        HoverElement.EnterHover(e);
                    }
                }

                if (PressedElement == null) { return; }

                if (!PressedElement.LocationComponent
                    .RenderSafeArea.Contains(InputSystem.MouseLocation)) {

                    PressedElement = null;
                }

            };

            InputSystem.MouseUp += delegate(Object o, MouseEventArgs e) {

                if (PressedElement == null) { return; }

                PressedElement.ExitPressed(e);
                PressedElement = null;
            };

            InputSystem.MouseDown += delegate(Object o, MouseEventArgs e) {

                if (HoverElement == null) { return; }

                if (FocusedElement != null) {
                    FocusedElement.ExitFocus(e);
                }
                FocusedElement = HoverElement;
                FocusedElement.EnterFocus(e);

                PressedElement = HoverElement;
                PressedElement.EnterPressed(e);
            };

            InputSystem.MouseDoubleClick += delegate(Object o, MouseEventArgs e) {

                if (HoverElement == null) { return; }

                if (FocusedElement != null) {
                    FocusedElement.ExitFocus(e);
                }
                FocusedElement = HoverElement;
                FocusedElement.EnterFocus(e);

                PressedElement = HoverElement;
                PressedElement.EnterPressed(e);
            };

            /* ## Input Events to fire the focused element's event handlers ## */

            InputSystem.CharEntered += delegate(Object o, CharacterEventArgs e) {
                if (FocusedElement != null) {
                    FocusedElement.CharEntered(e);
                }
            };

            InputSystem.KeyDown += delegate(Object o, KeyEventArgs e) {
                if (FocusedElement != null) {
                    FocusedElement.KeyDown(e);
                }
            };

            InputSystem.KeyUp += delegate(Object o, KeyEventArgs e) {
                if (FocusedElement != null) {
                    FocusedElement.KeyUp(e);
                }
            };

            InputSystem.MouseDoubleClick += delegate(Object o, MouseEventArgs e) {
                if (FocusedElement != null) {
                    FocusedElement.MouseDoubleClick(e);
                }
            };

            InputSystem.MouseDown += delegate(Object o, MouseEventArgs e) {
                if (FocusedElement != null) {
                    FocusedElement.MouseDown(e);
                }
            };

            InputSystem.MouseHover += delegate(Object o, MouseEventArgs e) {
                if (FocusedElement != null) {
                    FocusedElement.MouseHover(e);
                }
            };

            InputSystem.MouseMove += delegate(Object o, MouseEventArgs e) {
                if (FocusedElement != null) {
                    FocusedElement.MouseMove(e);
                }
            };

            InputSystem.MouseUp += delegate(Object o, MouseEventArgs e) {
                if (FocusedElement != null) {
                    FocusedElement.MouseUp(e);
                }


                if (FocusedElement != null 
                    && FocusedElement.LocationComponent.RenderBounds.Contains(
                        InputSystem.MouseLocation)) {

                    FocusedElement.MouseClick(e);
                }
            };

            InputSystem.MouseWheel += delegate(Object o, MouseEventArgs e) {
                if (FocusedElement != null) {
                    FocusedElement.MouseWheel(e);
                }
            };

            _rootNode = rootNode;
            HoverElement = _rootNode.Data;
        }

        /// <summary>
        /// Finds the element the mouse is currently being hovered over.
        /// </summary>
        private void FindHover(TreeNode<Element> node) {

            var renderBounds = node.Data.LocationComponent.RenderBounds;
            if (!renderBounds.Contains(InputSystem.MouseLocation)) {
                return;
            }

            if (node.Parent != null
                && !node.Parent.Data.LocationComponent.RenderSafeArea.Contains(
                    InputSystem.MouseLocation)) {
                return;
            }

            if (!node.Data.Active || !node.Data.Visible) {
                return;
            }

            HoverElement = node.Data;            
           
            foreach (var child in node.Children) {
                FindHover(child);
            }
        }
    }
}